/*
 *  FAT dir ops Initialization
 *
 *  Copyright(C) 2001
 *  Camilo Alejandro Arboleda
 *
 *  The contents of this file are distributed under the GNU General
 *  Public License version 2.
 *
 *  As a special exception, when this code is included in the RTEMS
 *  operating system, linking other files with RTEMS objects including
 *  this code does not cause the resulting executable application to
 *  be covered by the GNU General Public License. This exception does
 *  not however invalidate any other reasons why the executable file might
 *  be covered by the GNU General Public License.
 */

#include <dirent.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>
#include "fat.h"

#ifndef set_errno_and_return_minus_one
#define set_errno_and_return_minus_one( _error ) \
  do { errno = (_error); return -1; } while(0)
#endif

#define rtems_filesystem_is_separator( _ch ) \
   ( ((_ch) == '/') || ((_ch) == '\\') || ((_ch) == '\0'))

#define fat_is_not_valid_char( c ) \
   (((c < 'A') || (c > 'Z')) && ((c < 'a') || (c > 'z')) && ((c < '0') || (c >'9')) && (c!='.'))

#define fat_is_valid_char( c ) \
   (((c >= 'A') && (c =< 'Z')) || ((c >= 'a') && (c <= 'z')) || ((c >= '0') && (c <='9')) || (c=='.'))

#define upchar( c ) ( ((c >= 'a') && (c <= 'z')) ? (c-0x20):(c))
#define lowchar( c ) ( ((c >= 'A') && (c <= 'Z')) ? (c+0x20):(c))

extern void add_cluster(unsigned last_cluster, unsigned new_cluster, fat_geom_t *geom);
extern unsigned search_free_cluster(fat_geom_t *geom);
extern void save_fat( fat_geom_t *geom);

/*-------------------------------------------------------------------
 * Open directory
 */
int
FAT_dir_open(iop, pathname, flag, mode)
   rtems_libio_t  *iop;
   const char *pathname;
   unsigned flag;
   unsigned mode;
{
   FAT_jnode_t *node = iop->file_info;

   node->reference++;
   iop->offset = 0;

   return 0;
}

/*-------------------------------------------------------------------
 * Close directory
 */
int
FAT_dir_close(iop)
   rtems_libio_t  *iop;
{
   FAT_jnode_t *node = iop->file_info;

   node->reference--;
   if (node->reference <= 0)
      del_node(node);

   return 0;
}

int
add_cluster_to_dir(FAT_jnode_t *node)
{
   int moved;
   int cluster;
   unsigned char *buffer;

   if (!node->parent) return -1; /* Root directory can not grow */
   buffer = calloc(node->geometry->cluster_size, sizeof(char));
   if (!buffer) return -1;

   /*
   rtems_semaphore_obtain( node->geometry->mutex_id,
   RTEMS_WAIT,RTEMS_NO_TIMEOUT );
   */
   cluster = search_free_cluster(node->geometry);
   if (cluster < 2) { /* No room in disk */
      return -1;
   }
   add_cluster(node->current_cluster, cluster, node->geometry);
   /*
    rtems_semaphore_release( node->geometry->mutex_id );
    */
   save_fat(node->geometry);

   moved = fat_write(node, buffer, node->geometry->cluster_size);
   if (moved < node->geometry->cluster_size) {
      /* ???? */
   }

   return 0;
}

/*-------------------------------------------------------------------
 * Find a free directory entry
 */

int
find_free_entry(FAT_jnode_t *node)
{
   unsigned char loc_buffer[32];
   int offset;

   if (node->type != FAT_DIRECTORY) return -1;

   node->offset = 0;
   while (1) {
      if (fat_read(node,loc_buffer,32) <= 0) {
         offset = node->offset;
         return add_cluster_to_dir(node) ? -1:offset;
      }

      if (FAT_FREE_ENTRY(loc_buffer))
         return (node->offset-32);
   }

   return -1;
}

/*-------------------------------------------------------------------
 * Read directory
 */

int
fat_dir_read(FAT_jnode_t *node, char *buffer, int count, FAT_jnode_t *new_node)
{
   int offset,i,j;
   int result = -1;
   int bytes_moved = 0;
   unsigned char loc_buffer[64];
   struct dirent tmp_dirent;

   if (node->type != FAT_DIRECTORY) return -1;

   offset = node->offset;
   while (count) {
      result = -1;
      while (result) {
         if (fat_read(node,loc_buffer,32) <= 0) return -1;

         if (!FAT_DIRENTRY_VALID(loc_buffer))
            continue;
         tmp_dirent.d_ino = FAT_DIRENTRY_FIRST_CLUSTER(loc_buffer);
         tmp_dirent.d_off = offset;
         tmp_dirent.d_reclen = sizeof(struct dirent);
         for (i=0, j=0; i< 11 ; i++ ) {
            if ( i == 8 && loc_buffer[i] != ' ' ) {
               tmp_dirent.d_name[j++] = '.';
            }
            if (loc_buffer[i] != ' ') {
               tmp_dirent.d_name[j++] = lowchar(loc_buffer[i]);
            }
         } /* end for */
         tmp_dirent.d_name[j] = '\0';
         tmp_dirent.d_namlen = j;
         memcpy(
                (void *)(buffer + bytes_moved),
                (void *)&tmp_dirent,
                sizeof( struct dirent )
               );
         bytes_moved += sizeof(struct dirent);
         result = 0;

      }
      count -= sizeof(struct dirent);
   }

   if (new_node) {
      strcpy(new_node->name,tmp_dirent.d_name);
      new_node->parent = node;
      new_node->reference = 0;
      new_node->size = FAT_DIRENTRY_FILELENGTH(loc_buffer);
      new_node->st_ino = FAT_DIRENTRY_FIRST_CLUSTER(loc_buffer);
      new_node->attrib = FAT_DIRENTRY_ATTRIB(loc_buffer);
      new_node->dir_offset = node->offset - 32;
      new_node->geometry   = node->geometry;
      if (new_node->attrib & 0x10) {
         new_node->type = FAT_DIRECTORY;
      } else {
         new_node->type = FAT_FILE;
      }
   } /* end if */

   return bytes_moved;
}

int
FAT_dir_read(iop, buffer, count)
   rtems_libio_t  *iop;
   void *buffer;
   int count;
{
   int bytes_moved = 0;
   FAT_jnode_t *node = iop->file_info;
   if (iop->offset != node->offset) {
      node->offset = iop->offset;
   }
   count = (count / sizeof(struct dirent)) * sizeof(struct dirent);
   bytes_moved = fat_dir_read(node, (char *)buffer, count, NULL);
   iop->offset = node->offset;
   return bytes_moved;
}



/* -----------------------------------------------------------------------
 * This routine will behave in one of three ways based on the state of 
 * argument whence. Based on the state of its value the offset argument will
 * be interpreted using one of the following methods:
 *
 * 	SEEK_SET - offset is the absolute byte offset from the start of the
 *                 logical start of the dirent sequence that represents the
 *                 directory
 * 	SEEK_CUR - offset is used as the relative byte offset from the current
 *                 directory position index held in the iop structure
 *	SEEK_END - N/A --> This will cause an assert.
 */

int FAT_dir_lseek(
  rtems_libio_t  *iop,
  off_t           offset,
  int             whence
)
{
   off_t normal_offset;

   normal_offset = (offset/32) * 32;


   switch( whence )
   {
   case SEEK_SET:	/* absolute move from the start of the file */
      iop->offset = normal_offset;
      break;

   case SEEK_CUR:	/* relative move */
      iop->offset = iop->offset + normal_offset;
      break;

   case SEEK_END:	/* Movement past the end of the directory via lseek */
      /* is not a permitted operation                     */
   default:
      set_errno_and_return_minus_one( EINVAL );
      break;

   }

   return 0;
}

/* -----------------------------------------------------------------------
 * This routine will obtain the following information concerning the current 
 * directory:
 * 	st_dev		0ll
 *	st_ino		1
 *  	st_mode		mode extracted from the jnode
 *	st_nlink	number of links to this node
 * 	st_uid		uid extracted from the jnode
 *	st_gid		gid extracted from the jnode
 * 	st_rdev		0ll
 *	st_size		the number of bytes in the directory 
 *			This is calculated by taking the number of entries
 *			in the directory and multiplying by the size of a 
 *			dirent structure
 *	st_blksize	0
 *   	st_blocks	0
 *	stat_atime	time of last access
 *	stat_mtime	time of last modification
 *	stat_ctime	time of the last change
 *
 * This information will be returned to the calling function in a -stat- struct
 *	
 */

int
FAT_dir_fstat( rtems_filesystem_location_info_t *loc, struct stat  *buf )
{
   FAT_jnode_t       *node;


   node = (FAT_jnode_t *) loc->node_access;

   buf->st_dev   = node->geometry->fd;
   buf->st_ino   = node->st_ino;
   buf->st_mode  = S_IRWXU | S_IRWXG | S_IRWXO | S_IWRITE | S_IREAD | S_IEXEC;
   if (node->type == FAT_DIRECTORY)
      buf->st_mode |= S_IFDIR;
   buf->st_nlink = 1;
   buf->st_uid   = 0;
   buf->st_gid   = 0;
   buf->st_rdev  = node->geometry->fd;
   buf->st_blksize = 0;
   buf->st_blocks  = 0;
   buf->st_atime = node->stat_atime;
   buf->st_mtime = node->stat_mtime;
   buf->st_ctime = node->stat_ctime;

   buf->st_size = 0;

   return 0;
}

